// gfx.cpp - 
#include "internal.h"
#include <wgal.h>
#include <wgal/gfx.h>

#include <map>

// vertex format
struct IMAGEVERTEX
{
	D3DXVECTOR3 pos;
	D3DCOLOR color;
	float tu, tv;
};

#define D3DFVF_IMAGEVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

// represents an image composed of multiple textures
typedef struct _COMPOSED_IMAGE
{
	// original surface
	LPDIRECT3DSURFACE9 surface;
	// array size (rows, cols)
	UINT rows; // number of rows
	UINT cols; // number of columns

	// image size (width, height)
	UINT width; // width of image
	UINT height; // height of image

	// individual texture information
	struct TEXTURE_INFO
	{
		LPDIRECT3DTEXTURE9 texture;
		UINT width;
		UINT height;
		IMAGEVERTEX vertices[4];
	}*textures; // array of textures
} COMPOSED_IMAGE, *LPCOMPOSED_IMAGE;

typedef std::map<WGALUInt32, LPDIRECT3DTEXTURE9> TextureMap;
typedef std::map<WGALUInt32, LPCOMPOSED_IMAGE> ImageMap;
typedef std::map<WGALUInt32, LPD3DXFONT> FontMap;

// direct3d device
LPDIRECT3DDEVICE9 d3ddev = NULL;
WGALBool gfx_init = FALSE;
D3DPRESENT_PARAMETERS d3dpp;

// device capabilities
WGALBool caps_retrieved = FALSE;
WGALBool tex_pow2;
WGALBool tex_square;
WGALUInt32 tex_mwidth;
WGALUInt32 tex_mheight;

// maps
static TextureMap textures;
static ImageMap images;
static FontMap fonts;

/*
struct RenderTask
{
	// next node	
	RenderTask *next;
		
	void *vertices;
	size_t vertex_count;
	size_t vertex_size;
	DWORD fvf;
	D3DPRIMITIVETYPE type;
	LPDIRECT3DTEXTURE9 texture;
	D3DXMATRIX mat_world;
};

static RenderTask *head = NULL;
*/

// preset unit vertices
IMAGEVERTEX unitVertices[][4] =
{
	{ // preset one : default
		{D3DXVECTOR3(0.0f,0.0f,0.0f),0xFFFFFFFF,0.0f,0.0f},
		{D3DXVECTOR3(1.0f,0.0f,0.0f),0xFFFFFFFF,1.0f,0.0f},
		{D3DXVECTOR3(1.0f,1.0f,0.0f),0xFFFFFFFF,1.0f,1.0f},
		{D3DXVECTOR3(0.0f,1.0f,0.0f),0xFFFFFFFF,0.0f,1.0f}
	},
	/* could be done with transformation
	{ // preset two : flipped horizontally
		{D3DXVECTOR3(0.0f,0.0f,0.0f),0xFFFFFFFF,1.0f,0.0f},
		{D3DXVECTOR3(1.0f,0.0f,0.0f),0xFFFFFFFF,0.0f,0.0f},
		{D3DXVECTOR3(1.0f,1.0f,0.0f),0xFFFFFFFF,0.0f,1.0f},
		{D3DXVECTOR3(0.0f,1.0f,0.0f),0xFFFFFFFF,1.0f,1.0f}
	},
	{ // preset three : flipped vertically
		{D3DXVECTOR3(0.0f,0.0f,0.0f),0xFFFFFFFF,0.0f,1.0f},
		{D3DXVECTOR3(1.0f,0.0f,0.0f),0xFFFFFFFF,1.0f,1.0f},
		{D3DXVECTOR3(1.0f,1.0f,0.0f),0xFFFFFFFF,1.0f,0.0f},
		{D3DXVECTOR3(0.0f,1.0f,0.0f),0xFFFFFFFF,0.0f,0.0f}
	},
	{ // preset four : upside down
		{D3DXVECTOR3(0.0f,0.0f,0.0f),0xFFFFFFFF,1.0f,1.0f},
		{D3DXVECTOR3(1.0f,0.0f,0.0f),0xFFFFFFFF,0.0f,1.0f},
		{D3DXVECTOR3(1.0f,1.0f,0.0f),0xFFFFFFFF,0.0f,0.0f},
		{D3DXVECTOR3(0.0f,1.0f,0.0f),0xFFFFFFFF,1.0f,0.0f}
	},
	*/
};

WGALUInt32 blend_mode = WGAL_BLEND_ALPHA;

WGALVoid SetRenderSettings(WGALBool lighting,
						   WGALBool zbuffer,
						   WGALUInt32 blendmode)
{

	d3ddev->SetRenderState(D3DRS_LIGHTING, lighting);
	d3ddev->SetRenderState(D3DRS_ZENABLE, zbuffer ? D3DZB_TRUE : D3DZB_FALSE);

	switch(blendmode)
	{
	case WGAL_BLEND_NONE:
		// disable blending
		d3ddev->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_MODULATE);
		d3ddev->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
		d3ddev->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_DIFFUSE);
		d3ddev->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_DISABLE);
		d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
		d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
		d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);
		break;
	case WGAL_BLEND_ALPHA:
		// alpha blending options
		d3ddev->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_MODULATE);
		d3ddev->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
		d3ddev->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_DIFFUSE);
		d3ddev->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE);
		d3ddev->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_TEXTURE);
		d3ddev->SetTextureStageState(0,D3DTSS_ALPHAARG2,D3DTA_DIFFUSE);
		d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
		d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
		d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
		break;		
	case WGAL_BLEND_ADDITIVE:
		// additive blending options
		d3ddev->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_MODULATE);
		d3ddev->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
		d3ddev->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_DIFFUSE);
		d3ddev->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_DISABLE);
		d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
		d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
		d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
		break;
	}
	d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
}

WGALVoid OnDeviceLost()
{
	for(FontMap::iterator it = fonts.begin();
		it != fonts.end();
		it++)
	{
		it->second->OnLostDevice();
	}
}

WGALVoid OnDeviceReset()
{
	for(FontMap::iterator it = fonts.begin();
		it != fonts.end();
		it++)
	{
		it->second->OnResetDevice();
	}

	// reset renderer settings
	SetRenderSettings(FALSE, FALSE, blend_mode);
}

WGALBool CheckDeviceCaps()
{
	D3DCAPS9 d3dcaps;
	if(SUCCEEDED(d3ddev->GetDeviceCaps(&d3dcaps)))
	{
		// caps to check
		//TextureCaps
		// D3DPTEXTURECAPS_POW2, D3DPTEXTURECAPS_SQUAREONLY	
		//MaxTextureWidth 
		//MaxTextureHeight
		caps_retrieved = TRUE;
		tex_pow2 =
			TO_BOOLEAN(d3dcaps.TextureCaps & D3DPTEXTURECAPS_POW2);
		tex_square =
			TO_BOOLEAN(d3dcaps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY);
		tex_mwidth = d3dcaps.MaxTextureWidth;
		tex_mheight = d3dcaps.MaxTextureHeight;
		return TRUE;
	}
	return FALSE;
}

WGALAPI WGALVoid wgalSetBlendMode(WGALUInt32 mode)
{
	blend_mode = mode;
	SetRenderSettings(FALSE, FALSE, mode);
}

/* internal library functions */
// initializes WGAL Graphics component
WGALBool wgalGraphicsInit(HWND hWnd)
{
	CHECK_INITIALIZED(wgal_init);

	// check prerequisites
	if(!hWnd || !d3d)
		return FALSE;

	// already initialized
	if(gfx_init)
		return FALSE;

	WGALBool bWindowed = !wgalIsFullscreen();

	ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));
	d3dpp.Windowed = bWindowed;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.hDeviceWindow = hWnd;

	if(bWindowed)
	{
		// windowed mode
		d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
	}
	else
	{
		// fullscreen mode		
		d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
	}

	RECT rWndArea;

	GetClientRect(hWnd, &rWndArea);
	d3dpp.BackBufferWidth = rWndArea.right - rWndArea.left;
	d3dpp.BackBufferHeight = rWndArea.bottom - rWndArea.top;
	d3dpp.EnableAutoDepthStencil = TRUE;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

	// try creating device
	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT,
		D3DDEVTYPE_HAL,
		hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING,
		&d3dpp,
		&d3ddev)))
		return FALSE;

	// device created; check device capabilities
	CheckDeviceCaps();	

	// initialize render settings	
	SetRenderSettings(FALSE, FALSE, WGAL_BLEND_ALPHA);

	gfx_init = TRUE;

	return TRUE;
}

// deinitializes WGAL Graphics component
WGALVoid wgalGraphicsDeinit()
{
	// not initialized yet
	if(!gfx_init)
		return;

	//wgalFreeTextureAll();
	wgalFreeImageAll();
	wgalFreeFontAll();

	gfx_init = FALSE;
}

/* static functions */
// creates D3D surface from file
static BOOL CreateSurfaceFromFile(LPDIRECT3DSURFACE9 *ppSurface,
								  WGALCString szFilename,
								  D3DCOLOR colorKey = 0)
{
	// check supplied parameters
	if(ppSurface == NULL)
		return FALSE;

	HRESULT hRes;
	D3DXIMAGE_INFO srcInfo;
	LPDIRECT3DSURFACE9 pSurface;

	// retrieve image information
	if(D3DXGetImageInfoFromFileW(szFilename, &srcInfo) != D3D_OK)
		return FALSE;

	// create surface
	hRes = d3ddev->CreateOffscreenPlainSurface(
		srcInfo.Width,
		srcInfo.Height,
		D3DFMT_A8R8G8B8,
		D3DPOOL_SCRATCH,
		&pSurface,
		NULL);

	if(hRes != D3D_OK)
		return FALSE;

	// load surface
	hRes = D3DXLoadSurfaceFromFileW(
		pSurface,
		NULL,
		NULL,
		szFilename,
		NULL,
		D3DX_DEFAULT,
		colorKey,
		NULL);

	if(hRes != D3D_OK)
	{
		pSurface->Release();

		return FALSE;
	}

	*ppSurface = pSurface;

	return TRUE;
}

// creates D3D surface from memory
static BOOL CreateSurfaceFromFileInMemory(LPDIRECT3DSURFACE9 *ppSurface,
										  const WGALVoid * pSrcData,
										  WGALUInt32 srcDataSize,
										  D3DCOLOR colorKey = 0)
{
	// check supplied parameters
	if(ppSurface == NULL)
		return FALSE;

	HRESULT hRes;
	D3DXIMAGE_INFO srcInfo;
	LPDIRECT3DSURFACE9 pSurface;

	// retrieve image information
	if(D3DXGetImageInfoFromFileInMemory(pSrcData, srcDataSize, &srcInfo) != D3D_OK)
		return FALSE;

	// create surface
	hRes = d3ddev->CreateOffscreenPlainSurface(
		srcInfo.Width,
		srcInfo.Height,
		D3DFMT_A8R8G8B8,
		D3DPOOL_SCRATCH,
		&pSurface,
		NULL);

	if(hRes != D3D_OK)
		return FALSE;

	// load surface
	hRes = D3DXLoadSurfaceFromFileInMemory(
		pSurface,
		NULL,
		NULL,
		pSrcData,
		srcDataSize,
		NULL,
		D3DX_DEFAULT,
		colorKey,
		NULL);

	if(hRes != D3D_OK)
	{
		pSurface->Release();

		return FALSE;
	}

	*ppSurface = pSurface;

	return TRUE;
}

/* exported functions */
// loads 3D texture from file
WGALAPI WGALUInt32 wgalLoadTexture(WGALCString path)
{
	CHECK_INITIALIZED(gfx_init);

	LPDIRECT3DTEXTURE9 d3dtex = NULL;

	HRESULT hRes = D3DXCreateTextureFromFile(d3ddev, path, &d3dtex);

	if(hRes == D3D_OK)
	{
		WGALUInt32 id = GetResourceId(WGAL_RESTYPE_TEXTURE);

		textures[id] = d3dtex;
		return id;
	}

	return ID_NONE;
}

// loads 3D texture from memory
WGALAPI WGALUInt32 wgalLoadTextureFromMemory(const WGALVoid * mem, WGALUInt32 size)
{
	CHECK_INITIALIZED(gfx_init);

	LPDIRECT3DTEXTURE9 d3dtex = NULL;

	HRESULT hRes = D3DXCreateTextureFromFileInMemory(d3ddev, mem, size, &d3dtex);

	if(hRes == D3D_OK)
	{
		WGALUInt32 id = GetResourceId(WGAL_RESTYPE_TEXTURE);

		textures[id] = d3dtex;
		return id;
	}

	return ID_NONE;
}

static WGALVoid FreeComposedImage(LPCOMPOSED_IMAGE image)
{
	if(image)
	{
		for(UINT i = 0;
			i < (image->cols * image->rows);
			i++)
		{
			// release each texture
			if(image->textures[i].texture)
				image->textures[i].texture->Release();
		}
		// free texture list
		free(image->textures);
		// free surface
		image->surface->Release();
		// free composed image object
		free(image);
	}
}

static WGALUInt32 SplitSurfaceIntoTextures(LPDIRECT3DSURFACE9 surface,
										   UINT tWidth,
										   UINT tHeight,
										   BOOL fixed)
{
	// query surface description
	D3DSURFACE_DESC desc;
	surface->GetDesc(&desc);

	// construct an image object
	LPCOMPOSED_IMAGE image = 
		(LPCOMPOSED_IMAGE)malloc(sizeof(COMPOSED_IMAGE));

	image->width = desc.Width;
	image->height = desc.Height;
	image->surface = surface;

	surface->AddRef();

	// calculate number of columns and rows
	image->cols = image->width / tWidth;
	image->rows = image->height / tHeight;

	// apply proper adjustments
	if(tWidth * image->cols < image->width)
		image->cols++;
	if(tHeight * image->rows < image->height)
		image->rows++;

	image->textures = (COMPOSED_IMAGE::TEXTURE_INFO *)calloc(
		image->cols * image->rows, sizeof(COMPOSED_IMAGE::TEXTURE_INFO)
		);

	// pointer to texture
	COMPOSED_IMAGE::TEXTURE_INFO *texture = image->textures;
	// source rect
	RECT srcRect;
	// destination rect
	RECT destRect = {0,0,};
	// texture surface
	LPDIRECT3DSURFACE9 texSurface;

	for(UINT row = 0; row < image->rows; row++)
	{
		for(UINT col = 0; col < image->cols; col++)
		{
			// TODO: apply all adjustable options			
			// calculate source dimensions
			srcRect.left = col * tWidth;
			srcRect.right = MIN(srcRect.left + tWidth, image->width);
			srcRect.top = row * tHeight;
			srcRect.bottom = MIN(srcRect.top + tHeight, image->height);

			destRect.right = srcRect.right - srcRect.left;
			destRect.bottom = srcRect.bottom - srcRect.top;

			// calculate texture dimensions
			if(fixed)
			{
				texture->width = tWidth;
				texture->height = tHeight;
			}
			else
			{
				texture->width = destRect.right;
				texture->height = destRect.bottom;
			}

			// create a new texture
			/*
			if(FAILED(d3ddev->CreateTexture(
				texture->width,			// width
				texture->height,		// height
				0,						// levels
				D3DUSAGE_AUTOGENMIPMAP,	// usage
				D3DFMT_A8R8G8B8,		// format
				D3DPOOL_MANAGED,		// pool
				&texture->texture,		// texture ref
				NULL					// reserved
				)))
			{
				// failed to create texture
				// perform cleanup
				FreeComposedImage(image);
				return ID_NONE;
			}*/
			if(FAILED(D3DXCreateTexture(
				d3ddev,
				texture->width,
				texture->height,
				D3DX_DEFAULT,
				D3DUSAGE_AUTOGENMIPMAP,
				D3DFMT_A8R8G8B8,
				D3DPOOL_MANAGED,
				&texture->texture
			)))
			{
				// failed to create texture
				// perform cleanup
				FreeComposedImage(image);
				return ID_NONE;
			}

			D3DSURFACE_DESC desc;
			texture->texture->GetLevelDesc(0, &desc);
			texture->width = desc.Width;
			texture->height = desc.Height;
			
			// get texture surface
			texture->texture->GetSurfaceLevel(0, &texSurface);			
			// copy partial data to new surface
			if(FAILED(D3DXLoadSurfaceFromSurface(
				texSurface,			// destination surface
				NULL,				// color palette
				&destRect,			// destination rect
				surface,			// source surface
				NULL,				// color palette
				&srcRect,			// source rect
				D3DX_FILTER_NONE,	// resize filter
				0					// colorkey
				)))
			{
				// failed to copy the data
				// perform cleanup
				FreeComposedImage(image);
				return ID_NONE;
			}
			
			// done; release texture surface
			texSurface->Release();
			// setup vertices
			memcpy(texture->vertices,
				unitVertices[0],
				sizeof(texture->vertices));

#define SETUP_NTH_VERTEX(N) {							\
	texture->vertices[N].pos.x =						\
		row * tWidth +									\
		texture->vertices[N].pos.x * destRect.right;	\
	texture->vertices[N].pos.y =						\
		col * tHeight +									\
		texture->vertices[N].pos.y * destRect.bottom;	\
	texture->vertices[N].tu *=							\
		(float)destRect.right / texture->width;			\
	texture->vertices[N].tv *=							\
		(float)destRect.bottom / texture->height;		\
}

	SETUP_NTH_VERTEX(0);
	SETUP_NTH_VERTEX(1);
	SETUP_NTH_VERTEX(2);
	SETUP_NTH_VERTEX(3);

#undef SETUP_NTH_VERTEX

			// signal the device to regenerate mipmap
			texture->texture->GenerateMipSubLevels();
			// proceed to next entry
			texture++;
		}
	}

	WGALUInt32 id = GetResourceId(WGAL_RESTYPE_IMAGE);
	images[id] = image;

	return id;
}

// loads image from file
WGALAPI WGALUInt32 wgalLoadImage(WGALCString path)
{
	CHECK_INITIALIZED(gfx_init);

	LPDIRECT3DSURFACE9 pSurface = NULL;

	if(!CreateSurfaceFromFile(&pSurface, path))
		return ID_NONE;

	WGALUInt32 id = SplitSurfaceIntoTextures(
		pSurface, tex_mwidth, tex_mheight, FALSE);

	pSurface->Release();

	return id;
}

// loads image from memory
WGALAPI WGALUInt32 wgalLoadImageFromMemory(const WGALVoid * mem, WGALUInt32 size)
{
	CHECK_INITIALIZED(gfx_init);

	LPDIRECT3DSURFACE9 pSurface = NULL;

	if(!CreateSurfaceFromFileInMemory(&pSurface, mem, size))
		return ID_NONE;

	WGALUInt32 id = SplitSurfaceIntoTextures(
		pSurface, tex_mwidth, tex_mheight, FALSE);

	pSurface->Release();

	return id;
}

WGALAPI WGALVoid wgalFreeImage(WGALUInt32 imgId)
{
	//CHECK_INITIALIZED(gfx_init);

	ImageMap::iterator it = images.find(imgId);

	if(it != images.end())
	{
		if(FreeResourceId(imgId) == 0)
		{
			FreeComposedImage(it->second);
			images.erase(it);
		}
	}
}

WGALAPI WGALVoid wgalFreeTexture(WGALUInt32 texId)
{
	//CHECK_INITIALIZED(gfx_init);

	TextureMap::iterator it = textures.find(texId);

	if(it != textures.end())
	{		
		if(FreeResourceId(texId) == 0)
		{
			it->second->Release();
			textures.erase(it);
		}
	}
}

WGALAPI WGALVoid wgalFreeImageAll()
{
	//CHECK_INITIALIZED(gfx_init);

	for(ImageMap::iterator it = images.begin();
		it != images.end();
		it++)
	{
		FreeComposedImage(it->second);
		while(FreeResourceId(it->first));
	}

	images.clear();
}

WGALAPI WGALVoid wgalFreeTextureAll()
{
	//CHECK_INITIALIZED(gfx_init);

	for(TextureMap::iterator it = textures.begin();
		it != textures.end();
		it++)
	{
		it->second->Release();
		while(FreeResourceId(it->first));
	}

	textures.clear();
}

// load a font with specified name
WGALAPI WGALUInt32 wgalLoadFont(WGALCString face,
								WGALUInt32 height,
								WGALBool bold,
								WGALBool italic)
{
	LPD3DXFONT font;
	// load a font from supplied parameters
	if(SUCCEEDED(D3DXCreateFontW(
		d3ddev,
		height,
		0,
		bold ? FW_BOLD : FW_NORMAL,
		D3DX_DEFAULT,
		italic,
		DEFAULT_CHARSET,
		OUT_DEFAULT_PRECIS,
		ANTIALIASED_QUALITY,
		DEFAULT_PITCH | FF_DONTCARE,
		face,
		&font)))
	{
		WGALUInt32 id = GetResourceId(WGAL_RESTYPE_FONT);

		fonts[id] = font;

		return id;
	}	

	return ID_NONE;
}

// get text width
WGALAPI WGALUInt32 wgalMeasureText(WGALUInt32 fontId,
								   WGALCString text)
{
	FontMap::iterator it = fonts.find(fontId);
	if(it != fonts.end())
	{
		LPD3DXFONT font = it->second;
		DWORD format = DT_SINGLELINE | DT_NOCLIP | DT_CALCRECT;
		RECT rect = {0,};

		font->DrawTextW(NULL, text, -1, &rect, format, 0);

		return rect.right - rect.left;
	}

	return 0;
}

// get font height
WGALAPI WGALUInt32 wgalGetFontHeight(WGALUInt32 fontId)
{
	FontMap::iterator it = fonts.find(fontId);
	if(it != fonts.end())
	{
		D3DXFONT_DESCW desc;
		if(SUCCEEDED(it->second->GetDescW(&desc)))
			return desc.Height;		
	}

	return 0;
}

// draw text with specified font, text, and position
WGALAPI WGALVoid wgalDrawText(WGALUInt32 fontId,
							  WGALCString text,
							  WGALLocationPtr pos,
							  WGALUInt32 align,
							  WGALColor color)
{
	FontMap::iterator it = fonts.find(fontId);
	if(it != fonts.end())
	{
		LPD3DXFONT font = it->second;
		DWORD format = DT_SINGLELINE | DT_NOCLIP;
		RECT rect = {0,};
		if(pos)
		{
			rect.left = (LONG)pos->x;
			rect.top = (LONG)pos->y;
		}

		// horizontal alignment
		if(align & WGAL_ALIGN_LEFT)
		{
			format |= DT_LEFT;
		}
		else if(align & WGAL_ALIGN_CENTER)
		{
			format |= DT_CENTER;
		}
		else if(align & WGAL_ALIGN_RIGHT)
		{
			format |= DT_RIGHT;
		}

		// vertical alignment
		if(align & WGAL_ALIGN_TOP)
		{
			format |= DT_TOP;
		}
		else if(align & WGAL_ALIGN_VCENTER)
		{
			format |= DT_VCENTER;
		}
		else if(align & WGAL_ALIGN_BOTTOM)
		{
			format |= DT_BOTTOM;
		}

		// draw text
		d3ddev->BeginScene();
		font->DrawTextW(NULL, text, -1, &rect, format, color);
		d3ddev->EndScene();
	}
}

// draw text within specified rect
WGALAPI WGALVoid wgalTextBox(WGALUInt32 fontId,
							 WGALCString text,
							 WGALRectPtr rect,
							 WGALUInt32 align,
							 WGALColor color,
							 WGALBool word_break)
{
	FontMap::iterator it = fonts.find(fontId);
	if(it != fonts.end())
	{
		LPD3DXFONT font = it->second;
		DWORD format = 0;
		RECT destRect = {0,};
		if(rect)
		{
			destRect.left = (LONG)rect->x;
			destRect.top = (LONG)rect->y;
			destRect.right = (LONG)(rect->x + rect->width);
			destRect.bottom = (LONG)(rect->y + rect->width);
		}

		// horizontal alignment
		if(align & WGAL_ALIGN_LEFT)
		{
			format |= DT_LEFT;
		}
		else if(align & WGAL_ALIGN_CENTER)
		{
			format |= DT_CENTER;
		}
		else if(align & WGAL_ALIGN_RIGHT)
		{
			format |= DT_RIGHT;
		}

		DWORD textHeight = font->DrawTextW(NULL, text, -1, &destRect, format & DT_CALCRECT, color);

		// vertical alignment
		if(align & WGAL_ALIGN_TOP)
		{
			format |= DT_TOP;
		}
		else if(align & WGAL_ALIGN_VCENTER)
		{
			format |= DT_VCENTER;
		}
		else if(align & WGAL_ALIGN_BOTTOM)
		{
			format |= DT_BOTTOM;
		}

		if(word_break)
		{
			format |= DT_WORDBREAK;
		}

		d3ddev->BeginScene();
		font->DrawTextW(NULL, text, -1, &destRect, format, color);
		d3ddev->EndScene();
	}
}

WGALAPI WGALVoid wgalFreeFont(WGALUInt32 fontId)
{
	//CHECK_INITIALIZED(gfx_init);

	FontMap::iterator it = fonts.find(fontId);

	if(it != fonts.end())
	{
		if(FreeResourceId(fontId) == 0)
		{
			it->second->Release();
			fonts.erase(it);
		}		
	}

}

WGALAPI WGALVoid wgalFreeFontAll()
{
	//CHECK_INITIALIZED(gfx_init);

	for(FontMap::iterator it = fonts.begin();
		it != fonts.end();
		it++)
	{
		it->second->Release();
		while(FreeResourceId(it->first));
	}

	fonts.clear();
}

// clear entire backbuffer
WGALAPI WGALVoid wgalClear(WGALColor color)
{
	CHECK_INITIALIZED(gfx_init);

	// clear backbuffer
	d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, color, 1.0f, 0);
	/*d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, 
		D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
		*/
}

// present backbuffer to frontbuffer
WGALAPI WGALVoid wgalPresent()
{
	CHECK_INITIALIZED(gfx_init);

	/*
	d3ddev->BeginScene();
	// perform batched blits
	while(head)
	{
		d3ddev->SetFVF(head->fvf);
		d3ddev->SetTransform(D3DTS_WORLD, &head->mat_world);
		d3ddev->SetTexture(0, head->texture);
		//d3ddev->DrawPrimitiveUP(head->type);
		
		// proceed to next
		head = head->next;
	}
	d3ddev->EndScene();
	*/

	d3ddev->Present(NULL, NULL, NULL, NULL);
}

/* stub; commented out
 * replaced with WGALBlit shorthand macro
// blit an image
WGALAPI WGALVoid wgalBlit(WGALUInt32 imgId,
						  WGALRectPtr srcRect,
						  WGALRectPtr destRect,
						  WGALFloat depth)
{
	//CHECK_INITIALIZED(gfx_init);
	wgalBlitEx(imgId, srcRect, destRect, WGAL_FLIP_NONE, 0.0f, NULL, depth);
}
*/

// blit an image
WGALAPI WGALVoid wgalBlitEx(WGALUInt32 imgId,
							//WGALRectPtr srcRect,
							//WGALRectPtr destRect,
							WGALFloat x,
							WGALFloat y,
							WGALFloat width,
							WGALFloat height,
							WGALUInt32 flip,
							WGALColor tint,
							WGALFloat rotation,
							WGALLocationPtr pivot/*,
							WGALFloat depth*/)
{
	CHECK_INITIALIZED(gfx_init);

	ImageMap::iterator imgEntry = images.find(imgId);
	if(imgEntry == images.end())
	{
		// specified image doesn't exist
		return;
	}
	// retrieve the requested image
	LPCOMPOSED_IMAGE image = imgEntry->second;	

	// note
	// NULL srcRect = entire image
	// NULL destRect = viewport size
	// NULL pivot = pivot is (0,0)

	D3DVIEWPORT9 viewport;

	d3ddev->GetViewport(&viewport);

	float viewportW = (float)viewport.Width;
	float viewportH = (float)viewport.Height;

	/*
	// default destination rectangle
	WGALRect defDest = {0,0,viewportW,viewportH};

	if(!destRect)
		destRect = &defDest;
		*/

	if(width == WGAL_DEFAULT)
		width = (float)image->width;
	if(height == WGAL_DEFAULT)
		height = (float)image->height;

	// default pivot
	WGALLocation defPivot = {0.0f, 0.0f};	

	if(!pivot)
		pivot = &defPivot;
	
	/*
	LPDIRECT3DVERTEXBUFFER9 buffer;
	d3ddev->CreateVertexBuffer(4*sizeof(IMAGEVERTEX),
		0,
		D3DFVF_IMAGEVERTEX,
		D3DPOOL_MANAGED,
		&buffer,
		NULL);

	LPVOID pBuf;

	buffer->Lock(0, 0, &pBuf, 0);
	*/
	
	/*
	memcpy(&pBuf,
		unitVertices[flip],
		sizeof(IMAGEVERTEX)*4);

	for(int i = 0; i < 4; i++)
	{
		// vertex color
		((IMAGEVERTEX *)pBuf)[i].color = tint;
	}
	*/

	float srcWidth = (float)image->width;
	float srcHeight = (float)image->height;

	// calculate horizontal/vertical ratio
	float hRatio = /*destRect->*/width / srcWidth;
	float vRatio = /*destRect->*/height / srcHeight;

	COMPOSED_IMAGE::TEXTURE_INFO *tex =
		image->textures;
	
	for(UINT row = 0; row < image->rows; row++)
	{
		for(UINT col = 0; col < image->cols; col++)
		{
			// iterate through textures
			//memcpy(pBuf, tex->vertices, sizeof(tex->vertices));
			
			for(int i = 0; i < 4; i++)
			{
				// vertex color
				//((IMAGEVERTEX *)pBuf)[i].color = tint;
				tex->vertices[i].color = tint;
			}

			// proceed to next
			tex++;
		}
	}
	
	/*
	if(srcRect)
	{
		float tu1, tv1, tu2, tv2;

		if(flip & WGAL_FLIP_HORIZONTAL)
		{
			tu1 = (srcRect->x + srcRect->width) / srcWidth;
			tu2 = srcRect->x / srcWidth;
		}
		else
		{
			tu1 = srcRect->x / srcWidth;
			tu2 = (srcRect->x + srcRect->width) / srcWidth;
		}

		if(flip & WGAL_FLIP_VERTICAL)
		{
			tv1 = (srcRect->y + srcRect->height) / srcHeight;
			tv2 = srcRect->y / srcHeight;
		}
		else
		{
			tv1 = srcRect->y / srcHeight;
			tv2 = (srcRect->y + srcRect->height) / srcHeight;
		}

		// texture coordinates : x
		// upper-left corner
		((IMAGEVERTEX *)pBuf)[0].tu = tu1;
		// upper-right corner
		((IMAGEVERTEX *)pBuf)[1].tu = tu2;
		// lower-right corner
		((IMAGEVERTEX *)pBuf)[2].tu = tu2;
		// lower-left corner
		((IMAGEVERTEX *)pBuf)[3].tu = tu1;

		// texture coordinates : y
		// upper-left corner
		((IMAGEVERTEX *)pBuf)[0].tv = tv1;
		// upper-right corner
		((IMAGEVERTEX *)pBuf)[1].tv = tv1;
		// lower-right corner
		((IMAGEVERTEX *)pBuf)[2].tv = tv2;
		// lower-left corner
		((IMAGEVERTEX *)pBuf)[3].tv = tv2;
	}
	*/

	//buffer->Unlock();

	// transform matrices
	D3DXMATRIXA16 matProjection;
	D3DXMATRIXA16 matIdentity;
	D3DXMATRIXA16 matWorld;	

	// world transform matrices
	D3DXMATRIXA16 matScaling;
	D3DXMATRIXA16 matTranslation;
	D3DXMATRIXA16 matPivot;
	D3DXMATRIXA16 matRotation;

	D3DXMatrixIdentity(&matIdentity);
	D3DXMatrixIdentity(&matWorld);
	D3DXMatrixOrthoOffCenterLH(&matProjection,
		0.0f + .5f, viewportW + .5f,
		viewportH + .5f, 0.0f + .5f,
		-1.0f, 1.0f);

	// set projection/view transform
	d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);
	d3ddev->SetTransform(D3DTS_VIEW, &matIdentity);

	// apply flip
	if(flip & WGAL_FLIP_HORIZONTAL)
	{
		hRatio = -hRatio;
		x += width;
	}
	if(flip & WGAL_FLIP_VERTICAL)
	{
		vRatio = -vRatio;
		y += height;
	}
	// scale image
	D3DXMatrixScaling(&matScaling,
		hRatio,
		vRatio,
		1.0f);

	// set pivot
	D3DXMatrixTranslation(&matPivot,
		-pivot->x, -pivot->y, 0.0f);

	// set rotation matrix
	D3DXMatrixRotationZ(&matRotation, DEGTORAD(rotation));

	// begin rendering
	if(FAILED(d3ddev->BeginScene()))
		return;

	d3ddev->SetFVF(D3DFVF_IMAGEVERTEX);

	tex = image->textures;

	// revert pivot and apply translation
	D3DXMatrixTranslation(&matTranslation,
		/*destRect->*/x + pivot->x,// - .5f,
		/*destRect->*/y + pivot->y,// - .5f,
		0.0f/*depth*/);
	
	// note: the order of multiplication matters
	matWorld *= matScaling;
	matWorld *= matPivot;
	matWorld *= matRotation;
	matWorld *= matTranslation;
	
	// finally set world transformation
	d3ddev->SetTransform(D3DTS_WORLD, &matWorld);

	//d3ddev->SetStreamSource(0, buffer, 0, sizeof(IMAGEVERTEX));

	for(UINT row = 0; row < image->rows; row++)
	{
		for(UINT col = 0; col < image->cols; col++)
		{
			// iterate through textures			
			d3ddev->SetTexture(0, tex->texture);

			// render it
			//d3ddev->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2);
			d3ddev->DrawPrimitiveUP(
				D3DPT_TRIANGLEFAN,
				2,
				tex->vertices,
				sizeof(tex->vertices[0]));

			// proceed to next
			tex++;
		}
	}

	// end rendering
	d3ddev->EndScene();

	//buffer->Release();
}